﻿package idv.cjcat.emitter {
	use namespace engine;
	
	import flash.display.*;
	import flash.errors.*;
	
	import idv.cjcat.emitter.ds.*;
	import idv.cjcat.emitter.fields.*;
	import idv.cjcat.emitter.geom.*;
	
	/**
	 * Abstract particle source.
	 * <p>
	 * This is an abstarct class and should not be instantiated.
	 * </p>
	 */
	public class ParticleSource implements IEmitterXML, IEmitterDestructible {
		
		public var active:Boolean = true;
		public var x:Number;
		public var y:Number;
		
		/** @private */
		engine var _particles:Vector.<Particle> = new Vector.<Particle>();
		/** @private */
		private var _bdVec:Vector.<BurstData> = new Vector.<BurstData>();
		
		public function ParticleSource(x:Number = 0, y:Number = 0) {
			this.id = null;
			
			this.x = x;
			this.y = y;
		}
		
		private var pd:ParticleData;
		private var reuseMC:Boolean;
		public function spawn(emitter:Emitter):void {
			for each (var p:Particle in _particles) {
				if (p._destroyed) EmitterGlobal.throwDestroyedObjectError(p);
				
				if (p._displayClass == null) continue;
				if (!p.active) continue;
				
				reuseMC = p.reuseMovieClip;
				
				var count:Number = 0;
				_bdVec = _bdVec.filter(bdFilter);
				for each (var bd:BurstData in _bdVec) {
					count += p.rate * bd.amount / bd.duration;
					bd.life -= 1 / _particles.length;
				}
				if (active) count += p.rate;
				count = Math.floor(count) + ((Math.random() < (count % 1))?(1):(0));
				for (var i:int = 0; i < count; i++) {
					
					pd = ParticleDataPool.get();
					if (reuseMC) {
						pd.particle = p._mcPool.get();
					} else {
						pd.particle = new p._displayClass();
					}
					pd.particleClass = p;
					pd.emitter = emitter;
					
					//set position
					var pos:MotionData2D = spawnPos();
					pd.cx = pos.x;
					pd.cy = pos.y;
					
					//set motion
					var velocity:Vector2D = p.direction.clone();
					velocity.rotateThis(EmitterMath.uniformSample(0, p.directionVar));
					velocity.length = EmitterMath.uniformSample(p.speed, p.speedVar);
					if (p.bidirectional) {
						if (Math.random() < 0.5) {
							velocity.x *= -1;
							velocity.y *= -1;
						}
					}
					
					pd.vx = velocity.x;
					pd.vy = velocity.y;
					pd.spin = EmitterMath.uniformSample(p.spin, p.spinVar);
					pd.oriented = p.oriented;
					pd.orientationOffset = p.orientationOffset;
					pd.velocityField = p.velocityField;
					pd.damping = p.damping;
					pd.mass = EmitterMath.uniformSample(p.mass, p.massVar);
					
					//set life
					pd.totalLife = EmitterMath.uniformSample(p.life, p.lifeVar);
					pd.life = pd.totalLife;
					
					//set transform
					pd.particle.rotation = EmitterMath.uniformSample(p.rotation, p.rotationVar);
					pd.scale = EmitterMath.uniformSample(p.scale, p.scaleVar);
					pd.particle.scaleX = pd.particle.scaleY = pd.scale;
					pd.initScale = EmitterMath.uniformSample(p.initScale, p.initScaleVar);
					pd.finalScale = EmitterMath.uniformSample(p.finalScale, p.finalScaleVar);
					pd.scaleGrowRange = EmitterMath.uniformSample(p.scaleGrowRange, p.scaleGrowRangeVar);
					pd.scaleDecayRange = EmitterMath.uniformSample(p.scaleDecayRange, p.scaleDecayRangeVar);
					pd.alpha = EmitterMath.uniformSample(p.alpha, p.alphaVar);
					pd.particle.alpha = pd.alpha;
					pd.initAlpha = EmitterMath.uniformSample(p.initAlpha, p.initAlphaVar);
					pd.finalAlpha = EmitterMath.uniformSample(p.finalAlpha, p.finalAlphaVar);
					pd.alphaGrowRange = EmitterMath.uniformSample(p.alphaGrowRange, p.alphaGrowRangeVar);
					pd.alphaDecayRange = EmitterMath.uniformSample(p.alphaDecayRange, p.alphaDecayRangeVar);
					
					//set bubble motion
					pd.bubbleMotion = p.bubbleMotion;
					pd.bubbleOriented = p.bubbleOriented;
					pd.bubbleAmplitude = EmitterMath.uniformSample(p.bubbleAmplitude, p.bubbleAmplitudeVar);
					pd.bubbleFrequency = EmitterMath.uniformSample(p.bubbleFrequency, p.bubbleFrequencyVar);
					pd.bubblePhase = EmitterMath.uniformSample(0, Math.PI);
					
					//push particle data into emitter
					emitter._pdVec.push(pd);
					
					switch (emitter.addChildMode) {
						case AddChildMode.TOP:
							emitter.container.addChild(pd.particle);
							break;
						case AddChildMode.BOTTOM:
							emitter.container.addChildAt(pd.particle, 0);
							break;
						default:
							emitter.container.addChildAt(pd.particle, Math.floor(Math.random() * emitter.container.numChildren));
							break;
					}
				}
			}
		}
		
		/**
		 * This is an abstract method and should be overriden.
		 */
		public function spawnPos():MotionData2D {
			return null;
		}
		
		/**
		 * Bursts an amount of particles suddenly.
		 * @param	count
		 * @param	duration
		 */
		public function burst(amount:Number = 15, duration:Number = 1):void {
			_bdVec.push(new BurstData(amount, duration));
		}
		
		private function bdFilter(bd:BurstData, index:int, vector:Vector.<BurstData>):Boolean {
			if (bd.life <= 0) return false;
			else return true;
		}
		
		/**
		 * Adds a particle design.
		 * @param	particle
		 */
		public function addParticle(particle:Particle):void {
			removeParticle(particle);
			_particles.push(particle);
		}
		
		/**
		 * Removes a particle design.
		 * @param	particle
		 */
		public function removeParticle(particle:Particle):void {
			var index:int = _particles.indexOf(particle);
			if (index >= 0) _particles.splice(index, 1);
		}
		
		/**
		 * Removes all particle designs.
		 * @return The original particle designs.
		 */
		public function clearParticles():Vector.<Particle> {
			var temp:Vector.<Particle> = _particles;
			_particles = new Vector.<Particle>();
			return temp;
		}
		
		
		//IEmitterDestructible
		//------------------------------------------------------------------------------------------------
		
		engine var _destroyed:Boolean = false;
		public function get destroyed():Boolean { return _destroyed }
		
		//------------------------------------------------------------------------------------------------
		//end of IEmitterDestructible
		
		
		//XML
		//------------------------------------------------------------------------------------------------
		
		/** @private */
		engine var _id:String;
		public function get id():String { return _id; }
		public function set id(value:String):void {
			delete EmitterGlobal._sources[_id];
			if (value == null) {
				while (EmitterGlobal._sources["source" + EmitterGlobal._sourceCounter] != undefined) {
					EmitterGlobal._sourceCounter++;
				}
				_id = "source" + EmitterGlobal._sourceCounter;
			} else {
				if (EmitterGlobal._sources[value] == undefined) {
					_id = value;
				} else {
					throw new IllegalOperationError("This name is already used by another source.");
				}
			}
			EmitterGlobal._sources[_id] = this;
		}
		public function toXML():XML {
			var xml:XML = <source/>;
			xml.@id = _id;
			xml.@type = "abstract";
			xml.@active = active.toString();
			xml.@x = x.toString();
			xml.@y = y.toString();
			
			var particlesXML:XML = <particles/>;
			for each (var p:Particle in _particles) {
				var pID:XML = <id/>;
				pID.@value = p._id;
				particlesXML.appendChild(pID);
			}
			xml.appendChild(particlesXML);
			
			return xml;
		}
		public function parseXML(xml:XML):void {
			active = (xml.@active == "true");
			x = parseFloat(xml.@x);
			y = parseFloat(xml.@y);
			
			for each (var node:XML in xml.particles.id) {
				addParticle(EmitterGlobal.getParticleByID(node.@value));
			}
		}
		
		//------------------------------------------------------------------------------------------------
		//end of XML
	}
}